package cc.minos.codec.utils {

	public class SimpleDateFormatter {
		/**
		 * SimpleDateFormatter.as
		 *
		 * An Actionscript 2 implementation of the Java SimpleDateFormat class.
		 * This code was directly adapted from Matt Kruse's Javascript class
		 * implementation, and is used/distributed with Matt's permission.
		 *
		 * Please report all bugs to Daniel Wabyick (dwabyick@fluid.com).
		 *
		 * @author Daniel Wabyick (Actionscript 2 port)
		 *                       http://www.fluid.com
		 * @author Matt Kruse ( Javascript implementation)
		 *                       http://www.JavascriptToolbox.com
		 *
		 * The following notice is maintained from Matt Kruse's
		 * original Javascript code.
		 *
		 * NOTICE: You may use this code for any purpose, commercial or
		 * private, without any further permission from the author. You may
		 * remove this notice from your final code if you wish, however it is
		 * appreciated by the author if at least my web site address is kept.
		 *

		 * HISTORY
		 * ------------------------------------------------------------------
		 * Oct 05, 2005 Wrapped into a static AS2 class - DWABYICK/FLUID
		 * May 17, 2003: Fixed bug in parseDate() for dates <1970
		 * March 11, 2003: Added parseDate() function
		 * March 11, 2003: Added "NNN" formatting option. Doesn't match up
		 *                 perfectly with SimpleDateFormat formats, but
		 *                 backwards-compatability was required.
		 *
		 * USAGE
		 * ------------------------------------------------------------------
		 * These functions use the same 'format' strings as the
		 * java.text.SimpleDateFormat class, with minor exceptions.
		 * The format string consists of the following abbreviations:
		 *
		 * Field        | Full Form          | Short Form
		 * -------------+--------------------+-----------------------
		 * Year         | yyyy (4 digits)    | yy (2 digits), y (2 or 4 digits)
		 * Month        | MMM (name or abbr.)| MM (2 digits), M (1 or 2 digits)
		 *              | NNN (abbr.)        |
		 * Day of Month | dd (2 digits)      | d (1 or 2 digits)
		 * Day of Week  | EE (name)          | E (abbr)
		 * Hour (1-12)  | hh (2 digits)      | h (1 or 2 digits)
		 * Hour (0-23)  | HH (2 digits)      | H (1 or 2 digits)
		 * Hour (0-11)  | KK (2 digits)      | K (1 or 2 digits)
		 * Hour (1-24)  | kk (2 digits)      | k (1 or 2 digits)
		 * Minute       | mm (2 digits)      | m (1 or 2 digits)
		 * Second       | ss (2 digits)      | s (1 or 2 digits)
		 * AM/PM        | a                  |
		 *
		 * NOTE THE DIFFERENCE BETWEEN MM and mm! Month=MM, not mm!
		 * Examples:
		 *  "MMM d, y" matches: January 01, 2000
		 *                      Dec 1, 1900
		 *                      Nov 20, 00
		 *  "M/d/yy"   matches: 01/20/00
		 *                      9/2/00
		 *  "MMM dd, yyyy hh:mm:ssa" matches: "January 01, 2000 12:30:45AM"
		 */

		public static var MONTH_NAMES:Array = new Array('January', 'February', 'March', 'April', 'May', 'June', 'July', 'August', 'September', 'October', 'November', 'December', 'Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec');
		public static var DAY_NAMES:Array = new Array('Sunday', 'Monday', 'Tuesday', 'Wednesday', 'Thursday', 'Friday', 'Saturday', 'Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat');

		public static function LZ(x:Number):String
		{
			var res:String = x.toString();
			if (x >= 0 && x <= 9)
				res = "0" + res;
			return res;
		}

		/**
		 * Convert a date object into a string using the given format.
		 * @param date The date value to convert.
		 * @param format The format of the date object. (e.g. "yyyy-MM-dd")
		 * @return The string value of the date.
		 */
		public static function formatDate(date:Date, format:String):String
		{
			format = format + "";
			var result:String = "";
			var i_format:int = 0;
			var c:String = "";
			var token:String = "";
			var y:Number = date.getFullYear();
			var M:Number = date.getMonth() + 1;
			var d:Number = date.getDate();
			var E:Number = date.getDay();
			var H:Number = date.getHours();
			var m:Number = date.getMinutes();
			var s:Number = date.getSeconds();
			// var yyyy,yy,MMM,MM,dd,hh,h,mm,ss,ampm,HH,H,KK,K,kk,k;
			// Convert real date parts into formatted versions
			var value:Object = new Object();
			if (String(y).length < 4)
			{
				y = ( y - 0 + 1900 );
			}
			value["y"] = "" + y;
			value["yyyy"] = y;
			value["yy"] = String(y).substring(2, 4);
			value["M"] = M;
			value["MM"] = LZ(M);
			value["MMM"] = MONTH_NAMES[M - 1];
			value["NNN"] = MONTH_NAMES[M + 11];
			value["d"] = d;
			value["dd"] = LZ(d);
			value["E"] = DAY_NAMES[E + 7];
			value["EE"] = DAY_NAMES[E];
			value["H"] = H;
			value["HH"] = LZ(H);
			if (H == 0)
			{
				value["h"] = 12;
			}
			else if (H > 12)
			{
				value["h"] = H - 12;
			}
			else
			{
				value["h"] = H;
			}
			value["hh"] = LZ(value["h"]);
			if (H > 11)
			{
				value["K"] = H - 12;
			}
			else
			{
				value["K"] = H;
			}
			value["k"] = H + 1;
			value["KK"] = LZ(value["K"]);
			value["kk"] = LZ(value["k"]);
			if (H > 11)
			{
				value["a"] = "PM";
			}
			else
			{
				value["a"] = "AM";
			}
			value["m"] = m;
			value["mm"] = LZ(m);
			value["s"] = s;
			value["ss"] = LZ(s);
			while (i_format < format.length)
			{
				c = format.charAt(i_format);
				token = "";
				while (( format.charAt(i_format) == c ) && ( i_format < format.length ))
				{
					token += format.charAt(i_format++);
				}
				if (value[token] != null)
				{
					result = result + value[token];
				}
				else
				{
					result = result + token;
				}
			}
			return result;
		}

		/**
		 * Determine if the a given string value is a date in the given format.
		 * @param A string value representing a date.
		 * @param format The format of this date. (e.g. "yyyy-MM-dd")
		 * @return true if date string matches format of format string and
		 * is a valid date. Else returns false.
		 */
		public static function isDate(val:String, format:String):Boolean
		{
			var date:Date = getDateFromFormat(val, format);
			if (date == null)
			{
				return false;
			}
			return true;
		}

		/**
		 *   Compare two date strings to see which is greater.
		 *   @param date1 A string representing the first date value.
		 *   @param dateformat1 The format of the first date. (e.g. "yyyy-MM-dd")
		 *   @param date2 A string representing the second date value.
		 *   @param dateformat2 The format of the second date.(e.g. "yyyy-MM-dd")
		 *   @return  1 if date1 >date2; 0 if date2 > date; -1 if either date is an invalid format.
		 */
		public static function compareDates(date1:String, dateformat1:String, date2:String, dateformat2:String):Number
		{
			var d1:Date = getDateFromFormat(date1, dateformat1);
			var d2:Date = getDateFromFormat(date2, dateformat2);
			if (d1 == null || d2 == null)
			{
				return -1;
			}
			else if (d1 > d2)
			{
				return 1;
			}
			return 0;
		}

		/**
		 * Get a date using the given format. If it does not match, it returns 0.
		 * @param val The string value to convert to a date
		 * @param format The format of the date object.
		 * @return The date in the given format, or null if the value doesn't match the given format.
		 */
		public static function getDateFromFormat(val:String, format:String):Date
		{
			val = val + "";
			format = format + "";
			var i_val:int = 0;
			var i_format:int = 0;
			var c:String = "";
			var token:String = "";
			var token2:String = "";
			var x:int;
			var y:int;
			var now:Date = new Date();
			var year:Number = now.getFullYear();
			var month:Number = now.getMonth() + 1;
			var date:int = 1;
			var hh:Number = now.getHours();
			var mm:Number = now.getMinutes();
			var ss:Number = now.getSeconds();
			var ampm:String = "";
			var i:int = 0;

			while (i_format < format.length)
			{
				// Get next token from format string
				c = format.charAt(i_format);
				token = "";
				while (( format.charAt(i_format) == c ) && ( i_format < format.length ))
				{
					token += format.charAt(i_format++);
				}
				// Extract contents of value based on format token
				if (token == "yyyy" || token == "yy" || token == "y")
				{
					if (token == "yyyy")
					{
						x = 4;
						y = 4;
					}
					if (token == "yy")
					{
						x = 2;
						y = 2;
					}
					if (token == "y")
					{
						x = 2;
						y = 4;
					}
					year = _getInt(val, i_val, x, y);
					i_val += String(year).length;
					if (String(year).length == 2)
					{
						if (year > 70)
						{
							year = 1900 + ( year - 0 );
						}
						else
						{
							year = 2000 + ( year - 0 );
						}
					}
				}
				else if (token == "MMM" || token == "NNN")
				{
					month = 0;
					for (i = 0; i < MONTH_NAMES.length; i++)
					{
						var month_name:String = MONTH_NAMES[i];
						if (val.substring(i_val, i_val + month_name.length).toLowerCase() == month_name.toLowerCase())
						{
							if (token == "MMM" || ( token == "NNN" && i > 11 ))
							{
								month = i + 1;
								if (month > 12)
								{
									month -= 12;
								}
								i_val += month_name.length;
								break;
							}
						}
					}
					if (( month < 1 ) || ( month > 12 ))
					{
						return null;
					}
				}
				else if (token == "EE" || token == "E")
				{
					for (i = 0; i < DAY_NAMES.length; i++)
					{
						var day_name:String = DAY_NAMES[i];
						if (val.substring(i_val, i_val + day_name.length).toLowerCase() == day_name.toLowerCase())
						{
							i_val += day_name.length;
							break;
						}
					}
				}
				else if (token == "MM" || token == "M")
				{
					month = _getInt(val, i_val, token.length, 2);
					if (( month < 1 ) || ( month > 12 ))
					{
						return null;
					}
					i_val += token.length;
				}
				else if (token == "dd" || token == "d")
				{
					date = _getInt(val, i_val, token.length, 2);
					if (( date < 1 ) || ( date > 31 ))
					{
						return null;
					}
					i_val += token.length;
				}
				else if (token == "hh" || token == "h")
				{
					hh = _getInt(val, i_val, token.length, 2);
					if (( hh < 1 ) || ( hh > 12 ))
					{
						return null;
					}
					i_val += token.length;
				}
				else if (token == "HH" || token == "H")
				{
					hh = _getInt(val, i_val, token.length, 2);
					if (( hh < 0 ) || ( hh > 23 ))
					{
						return null;
					}
					i_val += token.length;
				}
				else if (token == "KK" || token == "K")
				{
					hh = _getInt(val, i_val, token.length, 2);
					if (( hh < 0 ) || ( hh > 11 ))
					{
						return null;
					}
					i_val += token.length;
				}
				else if (token == "kk" || token == "k")
				{
					hh = _getInt(val, i_val, token.length, 2);
					if (( hh < 1 ) || ( hh > 24 ))
					{
						return null;
					}
					i_val += token.length;
					hh--;
				}
				else if (token == "mm" || token == "m")
				{
					mm = _getInt(val, i_val, token.length, 2);
					if (( mm < 0 ) || ( mm > 59 ))
					{
						return null;
					}
					i_val += token.length;
				}
				else if (token == "ss" || token == "s")
				{
					ss = _getInt(val, i_val, token.length, 2);
					if (( ss < 0 ) || ( ss > 59 ))
					{
						return null;
					}
					i_val += token.length;
				}
				else if (token == "a")
				{
					if (val.substring(i_val, i_val + 2).toLowerCase() == "am")
					{
						ampm = "AM";
					}
					else if (val.substring(i_val, i_val + 2).toLowerCase() == "pm")
					{
						ampm = "PM";
					}
					else
					{
						return null;
					}
					i_val += 2;
				}
				else
				{
					if (val.substring(i_val, i_val + token.length) != token)
					{
						return null;
					}
					else
					{
						i_val += token.length;
					}
				}
			}
			// If there are any trailing characters left in the value, it doesn't match
			if (i_val != val.length)
			{
				return null;
			}
			// Is date valid for month?
			if (month == 2)
			{
				// Check for leap year
				if ((( year % 4 == 0 ) && ( year % 100 != 0 ) ) || ( year % 400 == 0 ))
				{ // leap year
					if (date > 29)
					{
						return null;
					}
				}
				else
				{
					if (date > 28)
					{
						return null;
					}
				}
			}
			if (( month == 4 ) || ( month == 6 ) || ( month == 9 ) || ( month == 11 ))
			{
				if (date > 30)
				{
					return null;
				}
			}
			// Correct hours value
			if (hh < 12 && ampm == "PM")
			{
				hh = hh - 0 + 12;
			}
			else if (hh > 11 && ampm == "AM")
			{
				hh -= 12;
			}
			var newdate:Date = new Date(year, month - 1, date, hh, mm, ss);
			return newdate;
		}

		public static function fromNow(val:String, format:String):String
		{

			var d1:Date = getDateFromFormat(val, format);
			var d2:Date = new Date();

			var str:String = '';
			if (d1 > d2) str = 'later';
			else str = 'ago';

			var year:Number = Math.abs(d2.fullYear - d1.fullYear);
			var month:Number = Math.abs(d2.month - d1.month);
			var day:Number = Math.abs(d2.date - d1.date);

			if (year == 0 && month == 0)
			{
				if (day == 0)
				{
					str = 'Today';
				}
				else if (day == 1)
				{
					if (d1 > d2)
						str = "Tomorrow";
					else
						str = "Yesterday";
				}
			}
			else
			{
				if (year > 0)
				{
					if (year > 1)
						str = year + ' years ' + str;
					else
						str = year + ' year ' + str;
				}
				else if (month > 0)
				{
					if (month > 1)
						str = month + ' months ' + str;
					else
						str = month + ' month ' + str;
				}
				else if (day > 0)
				{
					if (day > 1)
						str = day + ' days ' + str;
					else
						str = day + ' day ' + str;
				}
			}
			trace(str)
			return str;
		}

		/**
		 * @return True if the value is an integer; false otherwise.
		 */
		private static function _isInteger(val:String):Boolean
		{
			var digits:String = "1234567890";
			for (var i:int = 0; i < val.length; i++)
			{
				if (digits.indexOf(val.charAt(i)) == -1)
				{
					return false;
				}
			}
			return true;
		}

		private static function _getInt(str:String, i:int, minlength:int, maxlength:int):Number
		{
			for (var x:int = maxlength; x >= minlength; x--)
			{
				var token:String = str.substring(i, i + x);
				if (token.length < minlength)
				{
					return NaN;
				}
				if (_isInteger(token))
				{
					return Number(token);
				}
			}
			return NaN;
		}
	}
}
